#include "Editor.h"

#include <sstream>
#include <commctrl.h>

#define ID_STATUS 1002

namespace
{
	Editor* m_gEditor = NULL;
}

LRESULT CALLBACK MainEditorProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if(m_gEditor)
		return m_gEditor->MsgProc(hwnd, msg, wParam, lParam);
	else
		return DefWindowProc(hwnd, msg, wParam, lParam);
}

Editor::Editor(HINSTANCE hInstance)
{
	m_hEditorWindow = NULL;
	m_hEditorInstance = hInstance;
	m_sEditorTitle = "Map Editor v1.0";
	m_EditorWidth = 1080;
	m_EditorHeight = 720;
	m_EditorStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
	m_gEditor = this;
}

Editor::~Editor(void)
{
	//HANDLE SHUTDOWN OF APPLICATION
	HR(m_pSwapChain->SetFullscreenState(FALSE, NULL));

	//Release members
	SAFE_RELEASE(m_pRenderTargetView);
	SAFE_RELEASE(m_pDepthStencilView);
	SAFE_RELEASE(m_pSwapChain);

	//Restore all default settings.
	if(m_pDeviceContext)
		m_pDeviceContext->ClearState();

	SAFE_RELEASE(m_pDeviceContext);
	SAFE_RELEASE(m_pDevice);
}

int Editor::Run()
{
	//Store counts per second for conversion factor.
	//USED BELOW TO CALCULATE DELTA TIME
	__int64 countsPerSec = 0;
	QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
	float secPerCount = 1.0f / countsPerSec;

	//Store prev time
	__int64 prevTime = 0;
	QueryPerformanceCounter((LARGE_INTEGER*)&prevTime);

	//This is the main application loop of a Win32 program
	//It uses a message based procedural system.
	MSG msg = {0};
	while(WM_QUIT != msg.message)
	{
		if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			//Calculate Delta Time
			__int64 curTime = 0;
			QueryPerformanceCounter((LARGE_INTEGER*)&curTime);
			float dt = (curTime - prevTime) * secPerCount;

			CalculateFPS(dt);
			//Update editor and child controls
			Update(dt);
			//Render content to editor
			Render();

			//Reset prev time for next loop
			prevTime = curTime;
		}
	}

	//Return error code when application exits
	return static_cast<int>(msg.wParam);
}

bool Editor::Init()
{
	//Initialize Editor Window
	if(!InitWindow())
		return false;

	if(!InitDirect3D())
		return false;

	return true;
}

bool Editor::InitWindow()
{
	INITCOMMONCONTROLSEX common_controls;
	common_controls.dwSize = sizeof(INITCOMMONCONTROLSEX);
	common_controls.dwICC = ICC_BAR_CLASSES;
	InitCommonControlsEx(&common_controls);


	//CREATE WNDCLASS STRUCT
	WNDCLASSEX wcex;
	ZeroMemory(&wcex, sizeof(WNDCLASSEX));
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.hInstance = m_hEditorInstance;
	wcex.lpfnWndProc = MainEditorProc;
	wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wcex.lpszClassName = "EDITORWNDCLASS";
	wcex.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
	wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

	ZeroMemory(&borderClass, sizeof(WNDCLASSEX));
	borderClass.cbClsExtra = 0;
	borderClass.cbWndExtra = 0;
	borderClass.style = CS_HREDRAW | CS_VREDRAW;
	borderClass.cbSize = sizeof(WNDCLASSEX);
	borderClass.hInstance = m_hEditorInstance;
	borderClass.lpfnWndProc = MainEditorProc;
	borderClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	HBRUSH borderBrush = CreateSolidBrush(RGB(238,238,224));
	borderClass.hbrBackground = borderBrush; /*(HBRUSH)GetStockObject(LTGRAY_BRUSH);*/
	borderClass.lpszClassName = "BORDERWNDCLASS";
	borderClass.lpszMenuName = NULL;

	//REGISTER THE WNDCLASS STRUCT
	if(!RegisterClassEx(&wcex))
	{
		MessageBox(NULL, "FAILED TO REGISTER EDITOR WNDCLASS", NULL, NULL);
		return false;
	}
	//REGISTER THE WNDCLASS STRUCT
	if(!RegisterClassEx(&borderClass))
	{
		MessageBox(NULL, "FAILED TO REGISTER EDITOR WNDCLASS", NULL, NULL);
		return false;
	}

	//STORE CORRECT CLIENT WIDTH/HEIGHT
	RECT r = {0, 0, m_EditorWidth, m_EditorHeight};
	AdjustWindowRect(&r, m_EditorStyle, TRUE);
	int width = r.right - r.left;
	int height = r.bottom - r.top;
	
	//CREATE EDITOR WINDOW
	m_hEditorWindow = CreateWindow("EDITORWNDCLASS", m_sEditorTitle.c_str(), m_EditorStyle,
					GetSystemMetrics(SM_CXSCREEN)/2 - width/2,
					GetSystemMetrics(SM_CYSCREEN)/2 - height/2,
					width, height, NULL, NULL, m_hEditorInstance, NULL);
	if(!m_hEditorInstance)
	{
		MessageBox(NULL, "FAILED TO CREATE EDITOR WINDOW", NULL, NULL);
		return false;
	}

	mapEditorControl = CreateWindowEx(WS_EX_CLIENTEDGE, "EDITORWNDCLASS", NULL, WS_CHILD |
		WS_VISIBLE | WS_BORDER | ES_LEFT | WS_HSCROLL | WS_VSCROLL, 200, 0, 800, 600, m_hEditorWindow,
		NULL, m_hEditorInstance, NULL);

	RECT client_rect;
	GetClientRect(m_hEditorWindow, &client_rect);
	statusControl = CreateWindowEx(0,  STATUSCLASSNAME,  NULL, WS_CHILD |WS_VISIBLE, 0, 
		client_rect.bottom-client_rect.top-20,20,client_rect.right-client_rect.left,
		m_hEditorWindow, NULL, m_hEditorInstance, NULL);

	borderControls[0] = CreateWindowEx(WS_EX_WINDOWEDGE, "BORDERWNDCLASS", NULL,
		WS_CHILD | WS_VISIBLE | WS_BORDER,
		0, 0, 200, 697, m_hEditorWindow, NULL, m_hEditorInstance, NULL);
	borderControls[1] = CreateWindowEx(WS_EX_CLIENTEDGE, "EDITORWNDCLASS", NULL,
		WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | ES_READONLY,
		200, 600, 800, 97, m_hEditorWindow, NULL, m_hEditorInstance, NULL);
	borderControls[2] = CreateWindowEx(WS_EX_WINDOWEDGE, "BORDERWNDCLASS", NULL,
		WS_CHILD | WS_VISIBLE | WS_BORDER,
		1000, 0, 280, 697, m_hEditorWindow, NULL, m_hEditorInstance, NULL);

	textureViewControl = CreateWindowEx(WS_EX_CLIENTEDGE, "EDITORWNDCLASS", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER
		| WS_VSCROLL, 10, 75, 180, 600, borderControls[0], NULL, m_hEditorInstance, NULL);

	char message[] = "THIS IS A STATUS BAR FOR THE MAP EDITOR blah blah blah blah blah blah";
	SendMessage(statusControl, SB_SETPARTS, sizeof(message)/sizeof(char), (LPARAM)message);
	SendMessage(statusControl, SB_SETTEXT, 0, (LPARAM)message);
	
	SendMessage(borderControls[1], WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), (LPARAM)0);
	//SendMessage( borderControls[1], EM_SETSEL, sizeof(message)/sizeof(char), (LPARAM)message);
	//SendMessage( hEdit, EM_REPLACESEL, FALSE, (LPARAM)text );
	//SendMessage(borderControls[1], EM_REPLACESEL, 0, (LPARAM)message);


	//SHOW EDITOR WINDOW
	ShowWindow(m_hEditorWindow, SW_SHOW);
	return true;
}


void Editor::CalculateFPS( float dt )
{
	static int frameCnt = 0;
	static float elapsedTime = 0.0f;

	frameCnt++;
	elapsedTime += dt;

	if(elapsedTime >= 1.0f)
	{
		float FPS = (float)frameCnt;

		std::stringstream ss;
		ss << m_sEditorTitle.c_str() << "   FPS: " << FPS;
		SetWindowText(m_hEditorWindow, ss.str().c_str());

		frameCnt = 0;
		elapsedTime = 0.0f;
	}
}

bool Editor::InitDirect3D()
{
	//Init create device flags
	UINT createDeviceFlags = 0;
#ifdef _DEBUG
	createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif


	//Cache possible feature levels
	D3D_FEATURE_LEVEL featureLevels[] = 
	{
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
		D3D_FEATURE_LEVEL_9_3
	};
	UINT numFeatureLevels = ARRAYSIZE(featureLevels);

	//Cache possible driver types
	D3D_DRIVER_TYPE driverTypes[] =
	{
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_REFERENCE
	};
	UINT numDriverTypes = ARRAYSIZE(driverTypes);

	RECT r;
	GetWindowRect(mapEditorControl, &r);
	int width = r.right - r.left;
	int height = r.bottom - r.top;

	//Create swap chain description
	DXGI_SWAP_CHAIN_DESC swapDesc;
	ZeroMemory(&swapDesc, sizeof(DXGI_SWAP_CHAIN_DESC));
	swapDesc.BufferDesc.Width = width;
	swapDesc.BufferDesc.Height = height;
	swapDesc.BufferDesc.RefreshRate.Numerator = 60;
	swapDesc.BufferDesc.RefreshRate.Denominator = 1;
	swapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	swapDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	swapDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	swapDesc.BufferCount = 1;
	swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swapDesc.OutputWindow = mapEditorControl;
	swapDesc.SampleDesc.Count = 1;
	swapDesc.SampleDesc.Quality = 0;
	swapDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	swapDesc.Windowed = true;
	swapDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;


	//Loop through driver types and try to create device and swap chain
	HRESULT hr;
	for(UINT i = 0; i < numDriverTypes; i++)
	{
		m_DriverType = driverTypes[i];
		hr = D3D11CreateDeviceAndSwapChain(NULL, m_DriverType, NULL, createDeviceFlags, featureLevels,
			numFeatureLevels, D3D11_SDK_VERSION, &swapDesc, &m_pSwapChain, &m_pDevice, &m_FeatureLevel, &m_pDeviceContext);
		if(SUCCEEDED(hr))
			break;
	}
	if(FAILED(hr))
	{
		MessageBox(NULL, "Failed to create device and swap chain", NULL, NULL);
		return false;
	}

	//Create render target view
	ID3D11Texture2D* m_pBackBuffer = 0;
	HR(m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&m_pBackBuffer)));
	/*_com_error error(hresult);
	LPCSTR errorText = error.ErrorMessage();
	MessageBox(NULL, errorText, NULL, NULL);*/
	HR(m_pDevice->CreateRenderTargetView(m_pBackBuffer, 0, &m_pRenderTargetView));
	SAFE_RELEASE(m_pBackBuffer);

	////Create depth stencil view

	//Step 1: Describe texture
	D3D11_TEXTURE2D_DESC dsDesc;
	dsDesc.Width = width;
	dsDesc.Height = height;
	dsDesc.MipLevels = 1;
	dsDesc.ArraySize = 1;
	dsDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	dsDesc.SampleDesc.Count = 1; //No MSAA
	dsDesc.SampleDesc.Quality = 0; //NO MSAA
	dsDesc.Usage = D3D11_USAGE_DEFAULT;
	dsDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	dsDesc.CPUAccessFlags = 0;
	dsDesc.MiscFlags = 0;

	//Step 2: Create Buffer and View
	ID3D11Texture2D* m_pDepthStencilBuffer = 0;
	HR(m_pDevice->CreateTexture2D(&dsDesc, NULL, &m_pDepthStencilBuffer));
	HR(m_pDevice->CreateDepthStencilView(m_pDepthStencilBuffer, NULL, &m_pDepthStencilView));

	//Step 3: Bind RenderView and DepthView
	m_pDeviceContext->OMSetRenderTargets(1, &m_pRenderTargetView, m_pDepthStencilView);

	////Setup viewport
	ZeroMemory(&m_Viewport, sizeof(D3D11_VIEWPORT));
	m_Viewport.TopLeftX = 0.0f;
	m_Viewport.TopLeftY = 0.0f;
	m_Viewport.Width = static_cast<float>(width);
	m_Viewport.Height = static_cast<float>(height);
	m_Viewport.MinDepth = 0.0f;
	m_Viewport.MaxDepth = 1.0f;

	m_pDeviceContext->RSSetViewports(1, &m_Viewport);

	return true;
}


